using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.CodeDom;
using System.ComponentModel;
using System.CodeDom.Compiler;

namespace Clarius.PresentationModel.Design
{
	internal class Generator
	{
		static readonly string ISupportInitializeName = typeof(ISupportInitialize).Name;
		static readonly string ISupportInitializeNotificationName = typeof(ISupportInitializeNotification).Name;
		static readonly string IChangeTrackingName = typeof(IChangeTracking).Name;
		static readonly string INotifyPropertyChangedName = typeof(INotifyPropertyChanged).Name;
		static readonly string INotifyPropertyChangingName = typeof(INotifyPropertyChanging).Name;

		static readonly CodeTypeReference ISupportInitializeRef = new CodeTypeReference(ISupportInitializeName);
		static readonly CodeTypeReference ISupportInitializeNotificationRef = new CodeTypeReference(ISupportInitializeNotificationName);
		static readonly CodeTypeReference IChangeTrackingRef = new CodeTypeReference(IChangeTrackingName);
		static readonly CodeTypeReference INotifyPropertyChangedRef = new CodeTypeReference(INotifyPropertyChangedName);
		static readonly CodeTypeReference INotifyPropertyChangingRef = new CodeTypeReference(INotifyPropertyChangingName);
		static readonly CodeAttributeDeclaration EditorBrowsableNever = new CodeAttributeDeclaration(
			new CodeTypeReference(typeof(EditorBrowsableAttribute).Name),
			new CodeAttributeArgument(
				new CodeFieldReferenceExpression(
					new CodeTypeReferenceExpression(typeof(EditorBrowsableState).Name),
					"Never"
				)
			)
		);

		bool isCSharp;
		bool is35;
		bool isDerivedModel;
		bool isSealed;
		bool hasChangeTracking;
		bool hasInitialization;

		CodeTypeDeclaration type;
		List<Property> publicProperties;
		List<string> implementedInterfaces;

		CodeStatement ifInitializingReturn = new CodeConditionStatement(
			new CodeBinaryOperatorExpression(
				new CodePropertyReferenceExpression(
					new CodeThisReferenceExpression(),
					"IsInitializing"), 
					CodeBinaryOperatorType.ValueEquality, 
				new CodePrimitiveExpression(true)),
			new CodeMethodReturnStatement());

		public Generator(CodeTypeDeclaration type, List<Property> publicProperties, 
			List<string> implementedInterfaces,
			bool isCSharp, bool is35, bool isDerivedModel, bool isSealed)
		{
			this.type = type;
			this.publicProperties = publicProperties;
			this.implementedInterfaces = implementedInterfaces;
			this.hasChangeTracking = implementedInterfaces.Contains(IChangeTrackingName);
			this.hasInitialization = implementedInterfaces.Contains(ISupportInitializeName);
			this.isCSharp = isCSharp;
			this.is35 = is35;
			this.isDerivedModel = isDerivedModel;
			this.isSealed = isSealed;
		}

		public void GenerateImplementation()
		{
			type.CustomAttributes.Add(new CodeAttributeDeclaration(
				new CodeTypeReference(typeof(GeneratedCodeAttribute)),
				new CodeAttributeArgument(new CodePrimitiveExpression(ThisAssembly.Title)),
				new CodeAttributeArgument(new CodePrimitiveExpression(ThisAssembly.Version))
			));

			if (isDerivedModel)
			{
				if (hasInitialization)
				{
					AddISupportInitializeOverride();
					AddISupportInitializeNotificationOverride();
				}
			}
			else
			{
				if (hasInitialization)
				{
					AddISupportInitialize();
					AddISupportInitializeNotification();
				}

				if (hasChangeTracking)
					AddIChangeTracking();

				AddINotifyPropertyChanged();
			}

			publicProperties.ForEach(prop =>
			{
				if (is35)
					AddPropertyChanging(prop);
				AddPropertyChanged(prop);
			});
		}

		private void AddISupportInitializeOverride()
		{
			if (publicProperties.Count > 0)
			{
				///// <summary>
				///// Initializes the nested properties that support <see cref="ISupportInitialize"/>.
				///// </summary>
				//[EditorBrowsable(EditorBrowsableState.Never)]
				//protected override void InitializeNested()
				//{
				//   base.InitializeNested();
				//   ISupportInitialize initializable;

				//   initializable = (ISupportInitialize)nested;
				//   initializable.BeginInit();
				//   initializable.EndInit();
				//}
				CodeMemberMethod initializeNested = new CodeMemberMethod();
				initializeNested.Name = "InitializeNested";
				initializeNested.Comments.Add(new CodeCommentStatement(
					"<summary>Initializes the nested properties that support <see cref='ISupportInitialize'/>.</summary>", true));
				initializeNested.CustomAttributes.Add(EditorBrowsableNever);
				initializeNested.Attributes = MemberAttributes.Family | MemberAttributes.Override;
				initializeNested.Statements.Add(
					new CodeMethodInvokeExpression(
						new CodeBaseReferenceExpression(),
						"InitializeNested"
					)
				);
				initializeNested.Statements.Add(
					new CodeVariableDeclarationStatement(ISupportInitializeRef, "initializable"));
				CodeExpression initializable = new CodeVariableReferenceExpression("initializable");
				foreach (Property prop in publicProperties)
				{
					if (prop.ShouldCast)
					{
						initializeNested.Statements.Add(
							new CodeAssignStatement(
								initializable,
								CastProperty(prop.Name, prop.ImplementsISupportInitialize, ISupportInitializeRef)
							)
						);
						initializeNested.Statements.Add(
							new CodeConditionStatement(
								new CodeBinaryOperatorExpression(
									initializable,
									CodeBinaryOperatorType.IdentityInequality,
									new CodePrimitiveExpression(null)
								),
								new CodeExpressionStatement(new CodeMethodInvokeExpression(
									initializable,
									"BeginInit")),
								new CodeExpressionStatement(new CodeMethodInvokeExpression(
									initializable,
									"EndInit"))
							)
						);
					}
				}
				type.Members.Add(initializeNested);
			}
		}

		private void AddISupportInitializeNotificationOverride()
		{
			///// <summary>
			///// Gets whether the object properties that support 
			///// <see cref="ISupportInitializeNotification"/> are initialized.
			///// </summary>
			//[EditorBrowsable(EditorBrowsableState.Never)]
			//protected override bool AreNestedInitialized
			//{
			//   get
			//   {
			//      return base.AreNestedInitialized && IsValueInitialized(nested as ISupportInitializeNotification);
			//   }
			//}
			CodeMemberProperty nestedInitialized = new CodeMemberProperty();
			nestedInitialized.Name = "AreNestedInitialized";
			nestedInitialized.Comments.Add(new CodeCommentStatement(
				" <summary>Gets whether the object properties that support <see cref='ISupportInitializeNotification'/> are initialized.</summary>", true));
			nestedInitialized.CustomAttributes.Add(EditorBrowsableNever);
			nestedInitialized.Attributes = MemberAttributes.Family | MemberAttributes.Override;
			nestedInitialized.Type = new CodeTypeReference(typeof(bool));

			CodeExpression retExpr = new CodePropertyReferenceExpression(new CodeBaseReferenceExpression(), nestedInitialized.Name);
			foreach (Property prop in publicProperties)
			{
				if (prop.ShouldCast)
				{
					CodeExpression cast = CastProperty(prop.Name,
						prop.ImplementsISupportInitializeNotification,
						ISupportInitializeNotificationRef);

					retExpr = new CodeBinaryOperatorExpression(
						retExpr,
						CodeBinaryOperatorType.BooleanAnd,
						new CodeMethodInvokeExpression(
							new CodeThisReferenceExpression(),
							"IsValueInitialized",
							cast
						)
					);
				}
			}
			nestedInitialized.GetStatements.Add(new CodeMethodReturnStatement(retExpr));
			type.Members.Add(nestedInitialized);
		}

		private void AddISupportInitialize()
		{
			if (isCSharp)
				type.BaseTypes.Add(ISupportInitializeRef);
			else
				type.BaseTypes.Add(typeof(ISupportInitialize));

			//private const string InitializationNotBegun = "Initialization has not been started.";
			CodeMemberField initializationNotBegun = new CodeMemberField(typeof(string), "InitializationNotBegun");
			initializationNotBegun.Attributes = MemberAttributes.Const;
			initializationNotBegun.InitExpression = new CodePrimitiveExpression("Initialization has not been started.");
			type.Members.Add(initializationNotBegun);

			//private const string NotInitialized = "The object has not been initialized properly.";
			CodeMemberField notInitialized = new CodeMemberField(typeof(string), "NotInitialized");
			notInitialized.Attributes = MemberAttributes.Const;
			notInitialized.InitExpression = new CodePrimitiveExpression("The object has not been initialized properly. Call Initialize prior to use.");
			type.Members.Add(notInitialized);

			//bool _initializing;
			type.Members.Add(new CodeMemberField(typeof(bool), "_initializing"));

			// protected bool IsInitializing { get { return _initializing; } }
			type.Members.Add(new CodeMemberProperty
			{
				Name = "IsInitializing", 
				Type = new CodeTypeReference(typeof(bool)),
				Attributes = MemberAttributes.Family,
				GetStatements =
				{
					new CodeMethodReturnStatement(
						new CodeFieldReferenceExpression(
							new CodeThisReferenceExpression(), 
							"_initializing")
					)
				}
			});

			//bool _isInitialized;
			type.Members.Add(new CodeMemberField(typeof(bool), "_isInitialized"));

			//private bool RequiresInitialize
			//{
			//   get
			//   {
			//		  var result = !IsInitialized;
			//		  result |= IsChanged;
			//   }
			//}
			CodeMemberProperty requiresInit = new CodeMemberProperty();
			requiresInit.Name = "RequiresInitialize";
			requiresInit.Type = new CodeTypeReference(typeof(bool));
			requiresInit.Attributes = MemberAttributes.Private;

			requiresInit.GetStatements.Add(
				new CodeVariableDeclarationStatement(
					typeof(bool), "result", 
					new CodeBinaryOperatorExpression(
						new CodePropertyReferenceExpression(
							new CodeThisReferenceExpression(),
							"IsInitialized"), 
						CodeBinaryOperatorType.ValueEquality, 
						new CodePrimitiveExpression(false)
					)
				)
			);

			if (hasChangeTracking)
			{
				requiresInit.GetStatements.Add(
					new CodeAssignStatement(
						new CodeVariableReferenceExpression("result"),
						new CodeBinaryOperatorExpression(
							new CodeVariableReferenceExpression("result"),
							CodeBinaryOperatorType.BooleanOr,
							new CodePropertyReferenceExpression(
								new CodeThisReferenceExpression(),
								"IsChanged"
							)
						)
					)
				);
			}

			requiresInit.GetStatements.Add(
				new CodeMethodReturnStatement(
					new CodeVariableReferenceExpression("result")));

			type.Members.Add(requiresInit);

			///// <summary>
			///// Initializes the object for use, by calling BeginInit and EndInit 
			///// if they haven't been initialized yet.
			///// </summary>
			//public void Initialize()
			//{
			//   if (RequiresInitialize)
			//   {
			//      BeginInit();
			//      EndInit();
			//		  InitializeNested();
			//      _isInitialized = true;
			//
			//      if (Initialized != null)
			//      {
			//         Initialized(this, EventArgs.Empty);
			//      }
			//   }
			//}
			CodeMemberMethod initialize = new CodeMemberMethod();
			initialize.Name = "Initialize";
			initialize.Attributes = MemberAttributes.Public | MemberAttributes.Final;
			initialize.Comments.Add(new CodeCommentStatement(
				"<summary>Initializes the object for use.</summary>", true));
			initialize.Statements.Add(
				new CodeConditionStatement(
					new CodePropertyReferenceExpression(
						new CodeThisReferenceExpression(),
						"RequiresInitialize"
					),
					new CodeExpressionStatement(
						new CodeMethodInvokeExpression(
							new CodeThisReferenceExpression(),
							"BeginInit"
						)
					),
					new CodeExpressionStatement(
						new CodeMethodInvokeExpression(
							new CodeThisReferenceExpression(),
							"InitializeNested"
						)
					),
					new CodeExpressionStatement(
						new CodeMethodInvokeExpression(
							new CodeThisReferenceExpression(),
							"EndInit"
						)
					)
				)
			);

			type.Members.Add(initialize);

			///// <summary>
			///// Initializes the nested properties that support <see cref="ISupportInitialize"/>.
			///// </summary>
			//[EditorBrowsable(EditorBrowsableState.Never)]
			//protected virtual void InitializeNested()
			//{
			//   ISupportInitialize initializable;

			//   initializable = (ISupportInitialize)nested;
			//   if (initializable != null)
			//   {
			//	    initializable.BeginInit();
			//     initializable.EndInit();
			//   }
			//}
			CodeMemberMethod initializeNested = new CodeMemberMethod();
			initializeNested.Name = "InitializeNested";
			initializeNested.Comments.Add(new CodeCommentStatement(
				"<summary>Initializes the properties whose values support <see cref='ISupportInitialize'/>.</summary>", true));
			initializeNested.CustomAttributes.Add(EditorBrowsableNever);
			if (isSealed)
				initializeNested.Attributes = MemberAttributes.Private;
			else
				initializeNested.Attributes = MemberAttributes.Family;

			if (publicProperties.Count > 0)
			{
				// To avoid adding the field if it's not used, which would 
				// generate a warning.
				bool addField = false;
				CodeExpression initializable = new CodeVariableReferenceExpression("initializable");
				foreach (Property prop in publicProperties)
				{
					if (prop.ShouldCast)
					{
						addField = true;
						initializeNested.Statements.Add(
							new CodeAssignStatement(
								initializable,
								CastProperty(prop.Name, prop.ImplementsISupportInitialize, ISupportInitializeRef)
							)
						);
						initializeNested.Statements.Add(
							new CodeConditionStatement(
								new CodeBinaryOperatorExpression(
									initializable,
									CodeBinaryOperatorType.IdentityInequality,
									new CodePrimitiveExpression(null)
								),
								new CodeExpressionStatement(new CodeMethodInvokeExpression(
									initializable,
									"BeginInit")),
								new CodeExpressionStatement(new CodeMethodInvokeExpression(
									initializable,
									"EndInit"))
							)
						);
					}
				}

				if (addField)
				{
					initializeNested.Statements.Insert(0, 
						new CodeVariableDeclarationStatement(ISupportInitializeRef, "initializable"));
				}
			}
			type.Members.Add(initializeNested);

			//public void BeginInit()
			//{
			//   _initializing = true;
			//}
			CodeMemberMethod beginInit = new CodeMemberMethod();
			beginInit.Name = "BeginInit";
			beginInit.Attributes = MemberAttributes.Public | MemberAttributes.Final;
			AddImplementationType(beginInit, ISupportInitializeRef);
			beginInit.Statements.Add(AssignField("_initializing", true));
			type.Members.Add(beginInit);

			//public void EndInit()
			//{
			//   if (!_initializing)
			//   {
			//      throw new InvalidOperationException(InitializationNotBegun);
			//   }
			//
			//   DoInitialize();
			//   _initializing = false;
			//}
			CodeMemberMethod endInit = new CodeMemberMethod();
			endInit.Name = "EndInit";
			endInit.Attributes = MemberAttributes.Public | MemberAttributes.Final;
			AddImplementationType(endInit, ISupportInitializeRef);
			endInit.Statements.Add(
				new CodeConditionStatement(
					new CodeBinaryOperatorExpression(
						new CodeFieldReferenceExpression(
							new CodeThisReferenceExpression(),
							"_initializing"
						),
						CodeBinaryOperatorType.ValueEquality,
						new CodePrimitiveExpression(false)
					),
					new CodeThrowExceptionStatement(
						new CodeObjectCreateExpression(
							typeof(InvalidOperationException).Name,
							new CodeFieldReferenceExpression(
								new CodeTypeReferenceExpression(type.Name),
								"InitializationNotBegun"
							)
						)
					)
				)
			);
			endInit.Statements.Add(
				new CodeMethodInvokeExpression(
					new CodeThisReferenceExpression(),
					"DoInitialize"
				)
			);

			if (hasChangeTracking)
				initialize.Statements.Add(
					new CodeExpressionStatement(
						new CodeMethodInvokeExpression(
							new CodeThisReferenceExpression(),
							"AcceptChanges"
						)
					)
				);

			endInit.Statements.Add(AssignField("_initializing", false));
			endInit.Statements.Add(AssignField("_isInitialized", true));
			endInit.Statements.Add(CallEventIfNotNull("Initialized"));

			type.Members.Add(endInit);
		}

		private void AddISupportInitializeNotification()
		{
			if (isCSharp)
				type.BaseTypes.Add(ISupportInitializeNotificationRef);
			else
				type.BaseTypes.Add(typeof(ISupportInitializeNotification));

			///// <summary>
			///// Signals that the object has been initialized.
			///// </summary>
			//public event EventHandler Initialized;
			CodeMemberEvent initialized = new CodeMemberEvent();
			initialized.Name = "Initialized";
			initialized.Attributes = MemberAttributes.Public;
			initialized.Type = new CodeTypeReference(typeof(EventHandler).Name);
			initialized.ImplementationTypes.Add(ISupportInitializeNotificationRef);
			initialized.Comments.Add(new CodeCommentStatement(
				"<summary>Signals that the object has been initialized.</summary>", true));
			type.Members.Add(initialized);

			//public bool IsInitialized
			//{
			//   get 
			//   {
			//      return _isInitialized && AreNestedInitialized;
			//   }
			//}
			CodeMemberProperty isInitialized = new CodeMemberProperty();
			isInitialized.Name = "IsInitialized";
			isInitialized.Type = new CodeTypeReference(typeof(bool));
			isInitialized.Attributes = MemberAttributes.Public | MemberAttributes.Final;
			AddImplementationType(isInitialized, ISupportInitializeNotificationRef);
			isInitialized.GetStatements.Add(new CodeMethodReturnStatement(
				new CodeBinaryOperatorExpression(
					new CodeFieldReferenceExpression(
						new CodeThisReferenceExpression(),
						"_isInitialized"
					),
					CodeBinaryOperatorType.BooleanAnd,
					new CodePropertyReferenceExpression(
						new CodeThisReferenceExpression(),
						"AreNestedInitialized"
					)
				)
			));
			type.Members.Add(isInitialized);

			///// <summary>
			///// Gets whether the object properties that support 
			///// <see cref="ISupportInitializeNotification"/> are initialized.
			///// </summary>
			//[EditorBrowsable(EditorBrowsableState.Never)]
			//protected virtual bool AreNestedInitialized
			//{
			//   get
			//   {
			//      return true && IsValueInitialized(nested as ISupportInitializeNotification);
			//   }
			//}
			CodeMemberProperty nestedInitialized = new CodeMemberProperty();
			nestedInitialized.Name = "AreNestedInitialized";
			nestedInitialized.Comments.Add(new CodeCommentStatement(
				" <summary>Gets whether the object properties that support <see cref='ISupportInitializeNotification'/> are initialized.</summary>", true));
			nestedInitialized.CustomAttributes.Add(EditorBrowsableNever);
			if (isSealed)
				nestedInitialized.Attributes = MemberAttributes.Private;
			else
				nestedInitialized.Attributes = MemberAttributes.Family;
			nestedInitialized.Type = new CodeTypeReference(typeof(bool));

			CodeExpression retExpr = new CodePrimitiveExpression(true);
			foreach (Property prop in publicProperties)
			{
				if (prop.ShouldCast)
				{
					CodeExpression cast = CastProperty(prop.Name,
						prop.ImplementsISupportInitializeNotification,
						ISupportInitializeNotificationRef);

					retExpr = new CodeBinaryOperatorExpression(
						retExpr,
						CodeBinaryOperatorType.BooleanAnd,
						new CodeMethodInvokeExpression(
							new CodeThisReferenceExpression(),
							"IsValueInitialized",
							cast
						)
					);
				}
			}
			nestedInitialized.GetStatements.Add(new CodeMethodReturnStatement(retExpr));
			type.Members.Add(nestedInitialized);

			///// <summary>
			///// Determines whether the value is null or its 
			///// <see cref="ISupportInitializeNotification.IsInitialized"/> is <see langword="true" />;
			///// </summary>
			//[EditorBrowsable(EditorBrowsableState.Never)]
			//protected bool IsValueInitialized(ISupportInitializeNotification child)
			//{
			//   return child == null || child.IsInitialized;
			//}
			CodeMemberMethod isValueInitialized = new CodeMemberMethod();
			isValueInitialized.Name = "IsValueInitialized";
			isValueInitialized.Comments.Add(new CodeCommentStatement(
				@"<summary>Determines whether the value is null or its 
				<see cref='ISupportInitializeNotification.IsInitialized'/> is <see langword='true' />.
				</summary>", true));
			isValueInitialized.CustomAttributes.Add(EditorBrowsableNever);
			if (isSealed)
				isValueInitialized.Attributes = MemberAttributes.Private;
			else
				isValueInitialized.Attributes = MemberAttributes.Family | MemberAttributes.Final;
			isValueInitialized.Parameters.Add(new CodeParameterDeclarationExpression(ISupportInitializeNotificationRef, "child"));
			isValueInitialized.ReturnType = new CodeTypeReference(typeof(bool));
			isValueInitialized.Statements.Add(
				new CodeMethodReturnStatement(
					new CodeBinaryOperatorExpression(
						new CodeBinaryOperatorExpression(
							new CodeArgumentReferenceExpression("child"),
							CodeBinaryOperatorType.IdentityEquality,
							new CodePrimitiveExpression(null)
						),
						CodeBinaryOperatorType.BooleanOr,
						new CodePropertyReferenceExpression(
							new CodeArgumentReferenceExpression("child"),
							"IsInitialized"
						)
					)
				)
			);
			type.Members.Add(isValueInitialized);
		}

		private void AddIChangeTracking()
		{
			if (isCSharp)
				type.BaseTypes.Add(IChangeTrackingRef);
			else
				type.BaseTypes.Add(typeof(IChangeTracking));

			//bool _isChanged;
			type.Members.Add(new CodeMemberField(typeof(bool), "_isChanged"));

			//public void AcceptChanges()
			//{
			//   isChanged = false;
			//   // foreach IChangeTracking nested property, call AcceptChanges.
			//   IChangeTracking child = ChildProperty as IChangeTracking;
			//   if (child != null) child.AcceptChanges();
			//}
			CodeMemberMethod acceptChanges = new CodeMemberMethod();
			acceptChanges.Name = "AcceptChanges";
			acceptChanges.Attributes = MemberAttributes.Public | MemberAttributes.Final;
			AddImplementationType(acceptChanges, IChangeTrackingRef);
			acceptChanges.Statements.Add(AssignField("_isChanged", false));
			
			if (publicProperties.Any(p => p.ShouldCast))
			{
					acceptChanges.Statements.Add(
						new CodeVariableDeclarationStatement(
							IChangeTrackingRef, 
							"tracked"
						)
					);
			}

			//   // foreach IChangeTracking nested property, call AcceptChanges.
			foreach (Property prop in publicProperties)
			{
				if (prop.ShouldCast)
				{
					CodeExpression cast = CastProperty(prop.Name,
						prop.ImplementsIChangeTracking,
						IChangeTrackingRef);
					// tracked = prop as IChangeTracking
					acceptChanges.Statements.Add(
						new CodeAssignStatement(
							new CodeVariableReferenceExpression("tracked"),
							cast)
					);
					// if (tracked != null) tracked.AcceptChanges()
					acceptChanges.Statements.Add(
						new CodeConditionStatement(
							new CodeBinaryOperatorExpression(
								new CodeVariableReferenceExpression("tracked"), 
								CodeBinaryOperatorType.IdentityInequality, 
								new CodePrimitiveExpression(null)
							), 
							new CodeExpressionStatement(
								new CodeMethodInvokeExpression(
									new CodeVariableReferenceExpression("tracked"), 
									"AcceptChanges"
								)
							)
						)
					);
				}
			}

			type.Members.Add(acceptChanges);

			//public bool IsChanged
			//{
			//   get
			//   {
			//      return _isChanged;
			//   }
			//}
			CodeMemberProperty isChanged = new CodeMemberProperty();
			isChanged.Name = "IsChanged";
			isChanged.Type = new CodeTypeReference(typeof(bool));
			isChanged.Attributes = MemberAttributes.Public | MemberAttributes.Final;
			AddImplementationType(isChanged, IChangeTrackingRef);
			isChanged.GetStatements.Add(
				new CodeMethodReturnStatement(
						new CodeFieldReferenceExpression(
							new CodeThisReferenceExpression(),
							"_isChanged")
				)
			);
			type.Members.Add(isChanged);
		}

		private void AddINotifyPropertyChanged()
		{
			if (isCSharp)
			{
				type.BaseTypes.Add(INotifyPropertyChangedRef);
				if (is35) 
					type.BaseTypes.Add(INotifyPropertyChangingRef);
			}
			else
			{
				type.BaseTypes.Add(typeof(INotifyPropertyChanged));
				if (is35) 
					type.BaseTypes.Add(typeof(INotifyPropertyChanging));
			}

			///// <summary>
			///// Signals that a property has changed.
			///// </summary>
			// public event PropertyChangedEventHandler PropertyChanged;
			type.Members.Add(new CodeMemberEvent
			{
				Type = new CodeTypeReference(typeof(PropertyChangedEventHandler).Name),
				Name = "PropertyChanged",
				Attributes = MemberAttributes.Public,
				ImplementationTypes = { INotifyPropertyChangedRef },
				Comments = 
				{	
					new CodeCommentStatement("<summary>Signals that a property has changed.</summary>", true)
				},
			});

			///// <summary>
			///// Raises the <see cref="PropertyChanged"/> event.
			///// </summary>
			///// <param name="property">Name of the property that changed.</param>
			//[EditorBrowsable(EditorBrowsableState.Never)]
			//protected void RaisePropertyChanged(string property)
			//{
			//   if (!_initializing) return;
			// 
			//   if (PropertyChanged != null)
			//      PropertyChanged(this, new PropertyChangedEventArgs(property));

			//   _isChanged = true;
			//}
			CodeMemberMethod raiseChanged = new CodeMemberMethod
			{
				Name = "RaisePropertyChanged",
				Comments = 
				{
					new CodeCommentStatement(
					@"<summary>Raises the <see cref='PropertyChanged'/> event.</summary>
					  <param name='property'>Name of the property that changed.</param>", true)
				},
				CustomAttributes = { EditorBrowsableNever },
				Parameters = { new CodeParameterDeclarationExpression(typeof(string), "property") },
				Attributes = isSealed ?
					MemberAttributes.Private :
					MemberAttributes.Family | MemberAttributes.Final,
				Statements =
				{
					new CodeConditionStatement(
						//if (PropertyChanged != null)
						new CodeBinaryOperatorExpression(
							new CodeEventReferenceExpression(new CodeThisReferenceExpression(), "PropertyChanged"),
							CodeBinaryOperatorType.IdentityInequality,
							new CodePrimitiveExpression(null)),
						new CodeExpressionStatement(
						//   PropertyChanged(this, new PropertyChangedEventArgs(property));
							new CodeDelegateInvokeExpression(
								new CodeEventReferenceExpression(new CodeThisReferenceExpression(), "PropertyChanged"),
								new CodeThisReferenceExpression(),
								new CodeObjectCreateExpression(
									typeof(PropertyChangedEventArgs).Name,
									new CodeArgumentReferenceExpression("property")
								)
							)
						)
					)
				}
			};

			if (hasChangeTracking)
			{
				// if (_isChanged == false)
				// {
				raiseChanged.Statements.Add(
					new CodeConditionStatement(
						new CodeBinaryOperatorExpression(
							new CodeFieldReferenceExpression(
								new CodeThisReferenceExpression(),
								"_isChanged"
							),
							CodeBinaryOperatorType.ValueEquality,
							new CodePrimitiveExpression(false)
						),
						// _isChanged = true;
						AssignField("_isChanged", true),
						// RaisePropertyChanged("IsChanged");
						new CodeExpressionStatement(
							new CodeMethodInvokeExpression(
								new CodeThisReferenceExpression(),
								"RaisePropertyChanged",
								new CodePrimitiveExpression("IsChanged"))
						)
					)
				);
				// }
			}

			if (hasInitialization)
			{
				// if (_initializing) return;
				raiseChanged.Statements.Insert(0, ifInitializingReturn);
			}

			type.Members.Add(raiseChanged);

			if (is35)
			{
				///// <summary>
				///// Signals that a property is changing.
				///// </summary>
				// public event PropertyChangingEventHandler PropertyChanging;
				type.Members.Add(new CodeMemberEvent
				{
					Type = new CodeTypeReference(typeof(PropertyChangingEventHandler).Name),
					Name = "PropertyChanging",
					Attributes = MemberAttributes.Public,
					ImplementationTypes = { INotifyPropertyChangingRef },
					Comments = 
					{	
						new CodeCommentStatement("<summary>Signals that a property is changing.</summary>", true)
					},
				});

				///// <summary>
				///// Raises the <see cref="PropertyChanging"/> event.
				///// </summary>
				///// <param name="property">Name of the property that is changing.</param>
				//[EditorBrowsable(EditorBrowsableState.Never)]
				//protected void RaisePropertyChanging(string property)
				//{
				//   if (PropertyChanging != null)
				//      PropertyChanging(this, new PropertyChangingEventArgs(property));
				//}
				var raiseChanging = new CodeMemberMethod
				{
					Name = "RaisePropertyChanging",
					Comments = 
				{
					new CodeCommentStatement(
					@"<summary>Raises the <see cref='PropertyChanging'/> event.</summary>
					  <param name='property'>Name of the property that is changing.</param>", true)
				},
					CustomAttributes = { EditorBrowsableNever },
					Parameters = { new CodeParameterDeclarationExpression(typeof(string), "property") },
					Attributes = isSealed ?
						MemberAttributes.Private :
						MemberAttributes.Family | MemberAttributes.Final,
					Statements = 
				{
					new CodeConditionStatement(
						//if (PropertyChanging != null)
						new CodeBinaryOperatorExpression(
							new CodeEventReferenceExpression(new CodeThisReferenceExpression(), "PropertyChanging"),
							CodeBinaryOperatorType.IdentityInequality,
							new CodePrimitiveExpression(null)),
						new CodeExpressionStatement(
						//   PropertyChanging(this, new PropertyChangingEventArgs(property));
							new CodeDelegateInvokeExpression(
								new CodeEventReferenceExpression(new CodeThisReferenceExpression(), "PropertyChanging"),
								new CodeThisReferenceExpression(),
								new CodeObjectCreateExpression(
									typeof(PropertyChangingEventArgs).Name,
									new CodeArgumentReferenceExpression("property")
								)
							)
						)
					)
				}
				};

				type.Members.Add(raiseChanging);

				if (hasInitialization)
				{
					// if (_initializing) return;
					raiseChanging.Statements.Insert(0, ifInitializingReturn);
				}
			}
		}

		private void AddPropertyChanged(Property property)
		{
			var raiser = AddPropertyChange(property.Name, property.Name + "Changed");

			if (property.ShouldCast)
			{
				// var inotify = this.DerivedObject as INotifyPropertyChanged;
				raiser.Statements.Add(new CodeVariableDeclarationStatement(
					INotifyPropertyChangedRef,
					"inotify",
					CastProperty(
						property.Name, 
						property.ImplementsINotifyPropertyChanged, 
						INotifyPropertyChangedRef)));

				// if (inotify != null)
				//   inotify.PropertyChanged += HandleDerivedObjectChanged;
				raiser.Statements.Add(new CodeConditionStatement(
					new CodeBinaryOperatorExpression(
						new CodeVariableReferenceExpression("inotify"),
						CodeBinaryOperatorType.IdentityInequality,
						new CodePrimitiveExpression(null)
					),
					new CodeAttachEventStatement(
						new CodeEventReferenceExpression(
							new CodeVariableReferenceExpression("inotify"), 
							"PropertyChanged"), 
						new CodeDelegateCreateExpression(
							new CodeTypeReference(typeof(PropertyChangedEventHandler).Name), 
							new CodeThisReferenceExpression(),
							"Handle" + property.Name + "Changed"
						)
					)
				));

				//private void HandleDerivedObjectChanged(object sender, PropertyChangedEventArgs args)
				//{
				//   
				//}
				type.Members.Add(new CodeMemberMethod
				{
					Name = "Handle" + property.Name + "Changed",
					Attributes = MemberAttributes.Private,
					Parameters =
					{
						new CodeParameterDeclarationExpression(
							typeof(object), "sender"), 
						new CodeParameterDeclarationExpression(
							new CodeTypeReference(typeof(PropertyChangedEventArgs).Name), 
							"args")
					},
					Statements =
					{
						CallEventIfNotNull(property.Name + "Changed"), 
						// RaisePropertyChanged/ing([prop.Name]);
						new CodeMethodInvokeExpression(
							new CodeThisReferenceExpression(),
							"RaisePropertyChanged",
							new CodePrimitiveExpression(property.Name))
					}
				});
			}
		}

		private void AddPropertyChanging(Property property)
		{
			var raiser = AddPropertyChange(property.Name, property.Name + "Changing");

			if (property.ShouldCast)
			{
				// var inotify = this.DerivedObject as INotifyPropertyChanged;
				raiser.Statements.Add(new CodeVariableDeclarationStatement(
					INotifyPropertyChangedRef,
					"inotify",
					CastProperty(
						property.Name,
						property.ImplementsINotifyPropertyChanged,
						INotifyPropertyChangedRef)));

				// if (inotify != null)
				//   inotify.PropertyChanged -= HandleDerivedObjectChanged;
				raiser.Statements.Add(new CodeConditionStatement(
					new CodeBinaryOperatorExpression(
						new CodeVariableReferenceExpression("inotify"),
						CodeBinaryOperatorType.IdentityInequality,
						new CodePrimitiveExpression(null)
					),
					new CodeRemoveEventStatement(
						new CodeEventReferenceExpression(
							new CodeVariableReferenceExpression("inotify"),
							"PropertyChanged"),
						new CodeObjectCreateExpression(
							new CodeTypeReference(typeof(PropertyChangedEventHandler).Name),
							new CodeMethodReferenceExpression(
								new CodeThisReferenceExpression(),
								"Handle" + property.Name + "Changed"
							)
						)
					)
				));
			}
		}

		private CodeMemberMethod AddPropertyChange(string propertyName, string eventName)
		{
			///// <summary>
			///// Signals that the property <see cref="[prop.Name]"/> has changed.
			///// </summary>
			// public event EventHandler [prop.Name]Changed;
			type.Members.Add(new CodeMemberEvent
			{
				Type = new CodeTypeReference(typeof(EventHandler).Name),
				Name = eventName,
				Attributes = MemberAttributes.Public,
				Comments = 
				{
					new CodeCommentStatement(String.Format(
						@"<summary>Signals that the property <see cref='{0}'/> {1}.</summary>", 
						propertyName,
						eventName.EndsWith("anged") ? "has changed" : "is changing"
						), true)
				}
			});

			///// <summary>
			///// Raises the <see cref="[prop.Name]Changed"/> event.
			///// </summary>
			// private void Raise[prop.Name]Changed()
			var raiser = new CodeMemberMethod
			{
				Name = "Raise" + eventName,
				Attributes = MemberAttributes.Private,
				Comments = 
				{
					new CodeCommentStatement(String.Format(
						@"<summary>Raises the <see cref='{0}'/> event.</summary>", eventName), true)
				},
				Statements = 
				{
					CallEventIfNotNull(eventName), 
					// RaisePropertyChanged/ing([prop.Name]);
					new CodeMethodInvokeExpression(
						new CodeThisReferenceExpression(),
						eventName.EndsWith("anged") ? "RaisePropertyChanged" : "RaisePropertyChanging",
						new CodePrimitiveExpression(propertyName))
				},
			};

			type.Members.Add(raiser);

			if (hasInitialization)
			{
				// if (_initializing) return;
				raiser.Statements.Insert(0, ifInitializingReturn);
			}

			return raiser;
		}

		private void AddImplementationType(CodeMemberMethod method, CodeTypeReference implementation)
		{
			if (!isCSharp)
				method.ImplementationTypes.Add(implementation);
			//   method.PrivateImplementationType = implementation;
			//else
		}

		private void AddImplementationType(CodeMemberProperty property, CodeTypeReference implementation)
		{
			if (isCSharp)
				property.ImplementationTypes.Add(implementation);
			//   property.PrivateImplementationType = implementation;
			//else
		}

		private CodeExpression CastProperty(string name, bool directCast, CodeTypeReference targetType)
		{
			CodeExpression cast;
			if (directCast)
			{
				// Normal cast is faster, so we 
				// do a direct cast if we could determine 
				// the property type already implements 
				// the interface.
				cast = new CodeCastExpression(
					targetType,
					new CodePropertyReferenceExpression(
						new CodeThisReferenceExpression(),
						name
					)
				);
			}
			else
			{
				// "as" cast is not supported in CodeDom. 
				// Must generate a codesnippet according to language.
				if (isCSharp)
				{
					cast = new CodeSnippetExpression(name + " as " + targetType.BaseType);
				}
				else
				{
					cast = new CodeSnippetExpression("TryCast(" + name + ", " + targetType.BaseType + ")");
				}
			}
			return cast;
		}

		private static CodeStatement AssignField(string fieldName, object value)
		{
			if (value is CodeExpression)
			{
				return new CodeAssignStatement(
					new CodeFieldReferenceExpression(
						new CodeThisReferenceExpression(),
						fieldName
					),
					(CodeExpression)value
				);
			}
			else
			{
				return new CodeAssignStatement(
					new CodeFieldReferenceExpression(
						new CodeThisReferenceExpression(),
						fieldName
					),
					new CodePrimitiveExpression(value)
				);
			}
		}

		private static CodeStatement CallEventIfNotNull(string eventName)
		{
			//   if (eventName != null)
			//   {
			//      eventName(this, EventArgs.Empty);
			//   }
			CodeEventReferenceExpression ev = new CodeEventReferenceExpression(
				new CodeThisReferenceExpression(), eventName);

			CodeConditionStatement notNull = new CodeConditionStatement(
				new CodeBinaryOperatorExpression(
					ev,
					CodeBinaryOperatorType.IdentityInequality,
					new CodePrimitiveExpression(null)
				),
				new CodeExpressionStatement(
					new CodeDelegateInvokeExpression(
						ev,
						new CodeThisReferenceExpression(),
						new CodeFieldReferenceExpression(
							new CodeTypeReferenceExpression(typeof(EventArgs).Name),
							"Empty"
						)
					)
				)
			);

			return notNull;
		}
	}
}
